/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package middleware;

/**
 * For handling serialization/deserialization
 * @author HO HOANG HUNG
 */
public class Serialization {

    private static final int MASK = 0xff;

    /**
     * Serialize the parameters for sending a request
     * @param paras
     * @param bytearray the byte array to store the serialized object
     * @param offset starting index
     * @return
     * @throws Exception
     */
    protected static int serializeParameters(Object[] paras, byte[] bytearray, int offset) throws Exception {
        bytearray[offset++] = (byte) paras.length;
        for (int i = 0; i < paras.length; i++) {
            System.out.println("Serialize parameter #" + i);
            offset = serializeObject(paras[i], bytearray, offset);
        }
        return offset;
    }

    /**
     * Serialize a single object for communication
     * @param input
     * @param bytearray the buffer to store the serialized object
     * @param offset starting index
     * @return the new index of the buffer
     * @throws Exception
     */
    protected static int serializeObject(Object input, byte[] bytearray, int offset) throws Exception {
        if (input == null) {
            bytearray[offset++] = -1;
        } else if (input instanceof Integer) {
            bytearray[offset++] = 0;
            int iint = (Integer) input;
            offset = putInt(iint, bytearray, offset);
        } else if (input instanceof Long) {
            bytearray[offset++] = 1;
            long iint = (Long) input;
            offset = putLong(iint, bytearray, offset);
        } else if (input instanceof Float) {
            bytearray[offset++] = 2;
            float iint = (Float) input;
            offset = putFloat(iint, bytearray, offset);
        } else if (input instanceof Double) {
            bytearray[offset++] = 3;
            double iint = (Double) input;
            offset = putDouble(iint, bytearray, offset);
        } else if (input instanceof Boolean) {
            bytearray[offset++] = 4;
            boolean iint = (Boolean) input;
            offset = putBoolean(iint, bytearray, offset);
        } else if (input instanceof String) {
            bytearray[offset++] = 5;
            String istr = (String) input;
            //  System.out.println("Serialize string "+istr);
            offset = putString(istr, bytearray, offset);
        } else if (input instanceof Serializable) {
            bytearray[offset++] = 6;
            Serializable srl = (Serializable) input;
            String prototypename = srl.getClass().getName();
            //    System.out.print("Customized class name is a "+prototypename + " " + srl.getClass().getSimpleName() + " " + srl.getClass());
            offset = putString(prototypename, bytearray, offset);
            offset = srl.serialize(bytearray, offset);
        } else {
            throw new Exception("Cannot serialize this object");
        }
        return offset;
    }

    protected static Object[] deserializeParameters(byte[] buf, int rbufpointer) throws Exception {
        int numOfParas = buf[rbufpointer++];
        Object[] paras = new Object[numOfParas];
        MutableInteger pointerobj = new MutableInteger();
        pointerobj.setValue(rbufpointer);
        for (int i = 0; i < paras.length; i++) {
            System.out.println("Deserialize object #" + i);
            paras[i] = deserializeObject(buf, pointerobj);
        }
        return paras;
    }

    protected static Object deserializeObject(byte[] buf, MutableInteger pointerobj) throws Exception {
        int rbufpointer = pointerobj.getValue();
        int type = buf[rbufpointer++];
        switch (type) {
            case -1:
                pointerobj.setValue(++rbufpointer);
                return null;
            case 0:
                MutableInteger bb = new MutableInteger();
                rbufpointer = getInt(bb, buf, rbufpointer);
                pointerobj.setValue(rbufpointer);
                return bb.getValue();
            case 1:
                MutableLong bbl = new MutableLong();
                rbufpointer = getLong(bbl, buf, rbufpointer);
                pointerobj.setValue(rbufpointer);
                return bbl.getValue();
            case 2:
                MutableFloat bbf = new MutableFloat();
                rbufpointer = getFloat(bbf, buf, rbufpointer);
                pointerobj.setValue(rbufpointer);
                return bbf.getValue();
            case 3:
                MutableDouble bbd = new MutableDouble();
                rbufpointer = getDouble(bbd, buf, rbufpointer);
                pointerobj.setValue(rbufpointer);
                return bbd.getValue();
            case 4:
                MutableBoolean bbb = new MutableBoolean();
                rbufpointer = getBoolean(bbb, buf, rbufpointer);
                pointerobj.setValue(rbufpointer);
                return bbb.getValue();
            case 5:
                MutableString bbs = new MutableString();
                rbufpointer = getString(bbs, buf, rbufpointer);
                String resstr = bbs.getValue();
                System.out.println("Deserialized string value is " + resstr);
                pointerobj.setValue(rbufpointer);
                return resstr;
            case 6:
                bbs = new MutableString();
                rbufpointer = getString(bbs, buf, rbufpointer);
                String protoname = bbs.getValue();
                System.out.println("Customized class name is " + protoname);
                Serializable prototype = (Serializable) Class.forName(protoname).newInstance();
                if (prototype == null) {
                    throw new Exception("Cannot find Serializable prototype for name " + protoname);
                }
                // Serializable pinstance = prototype.duplicate();
                rbufpointer = prototype.deserialize(buf, rbufpointer);
                pointerobj.setValue(rbufpointer);
                return prototype;
            default:
                throw new Exception("Unable to deserialize this object. Type = " + type);
        }
    }

    /**
     * Serialize an integer
     * @param input
     * @param buf buffer to store result
     * @param offset starting index
     * @return new offset value
     */
    public static int putInt(int input, byte[] buf, int offs) {
        //  System.out.print("putInt="+input+",offs="+offs+" input="+input);
        for (int i = 3; i >= 0; i--) {
            int offset = i * 8;
            buf[offs++] = (byte) ((input >>> offset) & MASK);
        }
        //    System.out.println("new offs="+offs);
        return offs;
    }

    public static int getInt(MutableInteger out, byte[] buf, int offset) {
        //     System.out.println("getInt:offset="+offset);
        int res = 0;
        for (int shifter = 3; shifter >= 0; shifter--) {
            res |= ((int) buf[offset++] & MASK) << (shifter * 8);
        }
        out.setValue(res);
        //    System.out.println("res="+res+",offset="+offset);
        return offset;
    }

    /**
     * Serialize a long
     * @param input
     * @param buf buffer to store result
     * @param offset starting index
     * @return new offset value
     */
    public static int putLong(long input, byte[] buf, int offs) {
        for (int i = 7; i >= 0; i--) {
            int offset = i * 8;
            buf[offs++] = (byte) ((input >>> offset) & MASK);
        }
        return offs;
    }

    /**
     * Deserialize the buffer and get a long value
     * @param out mutable long to store the result
     * @param buf buffer
     * @param offset starting index
     * @return new offset value
     */
    public static int getLong(MutableLong out, byte[] buf, int offset) {
        long res = 0;
        for (int shifter = 7; shifter >= 0; shifter--) {
            res |= ((long) buf[offset++] & MASK) << (shifter * 8);
        }
        out.setValue(res);
        return offset;
    }

    /**
     * Serialize a boolean
     * @param input
     * @param buf buffer to store result
     * @param offset starting index
     * @return new offset value
     */
    public static int putBoolean(boolean input, byte[] buf, int offset) {
        buf[offset++] = input ? (byte) 1 : (byte) 0;
        return offset;
    }

    /**
     * Deserialize the buffer and get a boolean value
     * @param out mutable long to store the result
     * @param buf buffer
     * @param offset starting index
     * @return new offset value
     */
    public static int getBoolean(MutableBoolean out, byte[] buf, int offset) {
        boolean res = false;
        res = buf[offset] == 1 ? true : false;
        out.setValue(res);
        offset++;
        return offset;
    }

    /**
     * Serialize an float
     * @param input
     * @param buf buffer to store result
     * @param offset starting index
     * @return new offset value
     */
    public static int putFloat(float input, byte[] buf, int offs) {
        int fl = Float.floatToIntBits(input);
        offs = putInt(fl, buf, offs);
        return offs;
    }

    /**
     * Deserialize the buffer and get a float value
     * @param out mutable long to store the result
     * @param buf buffer
     * @param offset starting index
     * @return new offset value
     */
    public static int getFloat(MutableFloat out, byte[] buf, int offset) {
        float res = 0;
        MutableInteger mi = new MutableInteger();
        offset = getInt(mi, buf, offset);
        int mii = mi.getValue();
        int s = ((mii >> 31) == 0) ? 1 : -1;
        int e = ((mii >> 23) & 0xff);
        int m = (e == 0) ? (mii & 0x7fffff) << 1 : (mii & 0x7fffff) | 0x800000;
        res = s * m * (float) (Math.pow(2, e - 150));
        out.setValue(res);
        return offset;
    }

    /**
     * Serialize a double
     * @param input
     * @param buf buffer to store result
     * @param offset starting index
     * @return new offset value
     */
    public static int putDouble(double input, byte[] buf, int offs) {
        long fl = Double.doubleToLongBits(input);
        offs = putLong(fl, buf, offs);
        return offs;
    }

    /**
     * Deserialize the buffer and get a double value
     * @param out mutable long to store the result
     * @param buf buffer
     * @param offset starting index
     * @return new offset value
     */
    public static int getDouble(MutableDouble out, byte[] buf, int offset) {
        double res = 0;
        MutableLong ml = new MutableLong();
        offset = getLong(ml, buf, offset);
        long mll = ml.getValue();
        int s = ((mll >> 63) == 0) ? 1 : -1;
        int e = (int) ((mll >> 52) & 0x7ffL);
        long m = (e == 0) ? (mll & 0xfffffffffffffL) << 1 : (mll & 0xfffffffffffffL) | 0x10000000000000L;
        res = s * m * (float) (Math.pow(2, e - 1075));
        out.setValue(res);
        return offset;
    }

    /**
     * Serialize a string
     * @param input
     * @param buf buffer to store result
     * @param offset starting index
     * @return new offset value
     */
    public static int putString(String input, byte[] buf, int offs) {
        //    System.out.println("putString "+input+", "+offs);
        int strlngth = input.length();
        if (strlngth > 127) {
            strlngth = 127;
        }
        char[] ichar = new char[strlngth];
        input.getChars(0, strlngth, ichar, 0);
        buf[offs++] = (byte) ichar.length;
        //    System.out.println("offs="+offs+",ichar="+buf[0]);
        for (int i = 0; i < ichar.length; i++) {
            int a = ichar[i];
            if (a > 127) {
                a = 95;
            }
            buf[offs++] = (byte) a;
            //      System.out.println("offs="+offs+",ichar="+buf[offs-1]);
        }
        return offs;
    }

    /**
     * Deserialize the buffer and get a string value
     * @param out
     * @param buf
     * @param offset
     * @return
     */
    public static int getString(MutableString out, byte[] buf, int offset) {
        //   System.out.println("getString offset="+offset);
        int length = buf[offset++];
        //    System.out.println("length="+length);
        char[] ichar = new char[length];
        for (int i = 0; i < length; i++) {
            ichar[i] = (char) buf[offset++];
            //         System.out.println("offset="+offset+" ichar[i]="+ichar[i]);
        }
        out.setValue(new String(ichar));
        return offset;
    }
}
